package com.analysis.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.analysis.common.FileUtil;
import com.analysis.model.Data;
import com.analysis.model.KLine;
import com.analysis.model.ZigzagMatch;
import com.analysis.service.ZigzagMatcheService;

public class ZigzagMatcheServiceImpl implements ZigzagMatcheService {

	private ZigzagServiceImpl zigzagService = new ZigzagServiceImpl();
	private StoreService storeService = StoreService.getInstance();

	private int getimin(double[] arr, int startpos, int endpos) {
		double temp = arr[0];
		int res = 0;
		for (int i = startpos; i <= endpos; i++) {
			if (arr[i] < temp) {
				temp = arr[i];
				res = i;
			}
		}
		return res;
	}

	private int getimax(double[] arr, int startpos, int endpos) {
		double temp = arr[0];
		int res = 0;
		for (int i = startpos; i <= endpos; i++) {
			if (arr[i] > temp) {
				temp = arr[i];
				res = i;
			}
		}
		return res;
	}

	private void normalize(double[] in, int len, int[] extreme, double[] out) {
		double min = in[getimin(in, 0, len - 1)];
		double max = in[getimax(in, 0, len - 1)];

		for (int i = 0; i < len; i++) {
			if (extreme[i] != 0) {
				out[i] = (in[i] - min) / (max - min);
			}
		}

		int prev_extreme = 0;
		for (int i = 1; i < len; i++) {
			if (extreme[i] != 0) {
				double k = (out[i] - out[prev_extreme]) / (i - prev_extreme);
				for (int j = prev_extreme + 1; j < i; j++) {
					out[j] = out[prev_extreme] + (j - prev_extreme) * k;
				}
				prev_extreme = i;
			}
		}
	}

	private double mean(double[] arr, int len) {
		double sum = 0;
		for (int i = 0; i < len; i++) {
			sum += arr[i];
		}
		return sum / len;
	}

	double stddev(double[] arr, int len) {
		double mu = mean(arr, len);
		double sum = 0;
		for (int i = 0; i < len; i++) {
			sum += (arr[i] - mu) * (arr[i] - mu);
		}
		double rt = Math.sqrt(sum / len);
		return rt;
	}

	public ZigzagMatch match(double[] targetPrices, int targetPricesLen, Data data) {
		ZigzagMatch res = new ZigzagMatch();

		int max_scalar = (int) (data.len / 2.0 / targetPricesLen);
		if (max_scalar > 10)
			max_scalar = 10;
		if (max_scalar <= 1.5)
			return res;

		int[] extreme1 = zigzagService.calZigzag(targetPrices, 2);
		double[] normalized1 = new double[targetPricesLen];
		normalize(targetPrices, targetPricesLen, extreme1, normalized1);
		double mu = mean(normalized1, targetPricesLen);
		double sigma = stddev(normalized1, targetPricesLen);
		for (int i = 0; i < targetPricesLen; i++) {
			normalized1[i] = (normalized1[i] - mu) / sigma;
		}

		double[] normalized2 = storeService.getNormalize(data.name);
		if (normalized2 == null || normalized2.length == 0) {
			int[] extreme2 = zigzagService.calZigzag(data.data, 5);
			normalized2 = new double[data.data.length];
			normalize(data.data, data.len, extreme2, normalized2);
			storeService.saveNormalize(data.name, normalized2);
		}

		int startpos = data.len / 2 - targetPricesLen / 2;
		double scalar = (max_scalar + 1) / 2.0;
		double T0 = 1000;
		double T = T0;
		double Tmin = 0.00001;
		double last_sim = Integer.MAX_VALUE;

		while (T > Tmin) {
			double _similarity = Integer.MAX_VALUE;
			int _startpos = 0;
			double _scalar = 0;

			for (int i = 0; i < 3; i++) {
				int __startpos = 0;
				double __scalar = 0;

				int rnd = new Random().nextInt(100) % 2;
				if (rnd == 1) {
					__scalar = (double) (((int) ((scalar + 0.1) * 10 - 10)) % (10 * (max_scalar - 1))) / 10 + 1;
				} else {
					__scalar = (double) (((int) ((scalar - 0.1) * 10 - 10)) % (10 * (max_scalar - 1))) / 10 + 1;
				}

				rnd = new Random().nextInt(100) % 2;
				if (rnd == 1) {
					__startpos = (startpos + data.len + 1) % (data.len - (int) (targetPricesLen * __scalar));
				} else {
					__startpos = (startpos + data.len - 1) % (data.len - (int) (targetPricesLen * __scalar));
				}
				int scalarLen = (int) (__scalar * targetPricesLen);
				double[] scaled = new double[scalarLen];
				scale(normalized1, targetPricesLen, __scalar, scaled);
				double __similarity = similarity(scaled, subArr(normalized2, __startpos),scalarLen);

				if (__similarity < _similarity) {
					_similarity = __similarity;
					_startpos = __startpos;
					_scalar = __scalar;
				}

			}

			if (_similarity < last_sim) {
				last_sim = _similarity;
				startpos = _startpos;
				scalar = _scalar;
			} else {
				double p = Math.exp((last_sim - _similarity) / T);
				int rnd = (int) ((new Random().nextInt() % 100000) / 100000.0);
				if (rnd < p) {
					last_sim = _similarity;
					startpos = _startpos;
					scalar = _scalar;
				}
			}

			T = 0.99 * T;
		}

		res.setScalar(scalar);
		res.setStartPos(startpos);
		res.setSimilarity(last_sim);
		return res;
	}

	private double[] subArr(double[] src, int startPos) {
		double[] target = new double[src.length - startPos];
		System.arraycopy(src, startPos, target, 0, src.length - startPos);
		return target;
	}

	double similarity(double[] in1, double[] in2, int len) {
		double mu = mean(in2, len);
		double sigma = stddev(in2, len);
		double[] normalized = new double[len];
		for (int i = 0; i < len; i++) {
			normalized[i] = (in2[i] - mu) / sigma;
		}

		double temp = 0;
		for (int i = 0; i < len; i++) {
			temp += (normalized[i] - in1[i]) * (normalized[i] - in1[i]);
		}

		return temp / len;
	}

	private void scale(double[] in, int len, double scalar, double[] out) {
		int outlen = (int) (len * scalar);
		for (int i = 0; i < outlen; i++) {
			double index = i * (len - 1) / (outlen - 1);
			int left = (int) Math.floor(index);
			int right = (int) Math.ceil(index);
			out[i] = in[left] + (index - left) * (in[right] - in[left]);
		}
	}

	public void run() {
		DataSetServiceImpl dataSet = new DataSetServiceImpl();
		KLineServiceImpl klineService = new KLineServiceImpl();
		List<String> set = dataSet.getSpecificDataSet("ea2d3");

		List<String> closes = FileUtil.readLines("./kline/input.txt");
		double[] in = new double[closes.size()];
		for (int i = 0; i < closes.size(); i++) {
			in[i] = Double.valueOf(closes.get(i));
		}

		List<ZigzagMatch> matches = new ArrayList<>();
		for (String symbol : set) {
			List<KLine> klines = klineService.getKline(symbol);
			ZigzagMatch match =match(in, in.length, klineToData(symbol, klines));
			match.setName(symbol);
			matches.add(match);
		}

	}

	public Data klineToData(String name, List<KLine> klines) {
		Data data = new Data();
		double[] closePrices = new double[klines.size()];
		for (int i = 0; i < klines.size(); i++) {
			closePrices[i] = klines.get(i).getClose();
		}

		data.data = closePrices;
		data.len = closePrices.length;
		data.name = name;
		return data;
	}

	public static void main(String[] args) {
		new ZigzagMatcheServiceImpl().run();
	}

}
